/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

import fs from "node:fs/promises";

import type { PackageJson } from "@fluidframework/build-tools";
import { ApiLevel, BaseCommand, knownApiLevels } from "../../library/index.js";
// AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule.
// eslint-disable-next-line import-x/no-internal-modules
import { readPackageJson, readTsConfig } from "../../library/package.js";

import {
	type Node10CompatExportData,
	getTypesPathFromPackage,
	// AB#8118 tracks removing the barrel files and importing directly from the submodules, including disabling this rule.
	// eslint-disable-next-line import-x/no-internal-modules
} from "../../library/packageExports.js";
import type { CommandLogger } from "../../logging.js";

export default class GenerateNode10EntrypointsCommand extends BaseCommand<
	typeof GenerateNode10EntrypointsCommand
> {
	static readonly description =
		`Generates node10 type declaration entrypoints for Fluid Framework API levels (/alpha, /beta, /internal etc.) as found in package.json "exports"`;

	public async run(): Promise<void> {
		const packageJson = await readPackageJson();

		const tsconfig = await readTsConfig();

		let emitDeclarationOnly = false;
		if (tsconfig.compilerOptions?.emitDeclarationOnly !== undefined) {
			emitDeclarationOnly = true;
		}

		const mapNode10CompatExportPathToData = mapExportPathsFromPackage(
			packageJson,
			emitDeclarationOnly,
			this.logger,
		);

		if (mapNode10CompatExportPathToData.size === 0) {
			throw new Error(
				'There are no API level "exports" requiring Node10 type compatibility generation.',
			);
		}

		await generateNode10TypeEntrypoints(mapNode10CompatExportPathToData, this.logger);
	}
}

export function mapExportPathsFromPackage(
	packageJson: PackageJson,
	emitDeclarationOnly: boolean,
	log: CommandLogger,
): Map<string, Node10CompatExportData> {
	const mapKeyToOutput = new Map<string, Node10CompatExportData>();

	// Iterate through exports looking for properties with values matching keys in map.
	for (const levels of knownApiLevels) {
		// Exclude root "." path as "types" should handle that.
		if (levels === ApiLevel.public) {
			continue;
		}

		const typesPath = getTypesPathFromPackage(packageJson, levels, log);

		if (typesPath === undefined) {
			continue;
		}

		const node10ExportPath = typesPath
			.replace(/\/index(\.d\.[cm]?ts)?$/, "/internal$1")
			.replace(/^.*\//, "");

		mapKeyToOutput.set(node10ExportPath, {
			relPath: typesPath,
			isTypeOnly: emitDeclarationOnly,
		});
	}

	return mapKeyToOutput;
}

const generatedHeader: string = `/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

/*
 * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 * Generated by "flub generate node10Entrypoints" in @fluid-tools/build-cli.
 */

`;

async function generateNode10TypeEntrypoints(
	mapExportPathToData: Map<string, Node10CompatExportData>,
	log: CommandLogger,
): Promise<void> {
	/**
	 * List of out file save promises. Used to collect generated file save
	 * promises so we can await them all at once.
	 */
	const fileSavePromises: Promise<void>[] = [];

	for (const [outFile, { relPath, isTypeOnly }] of mapExportPathToData.entries()) {
		log.info(`\tGenerating ${outFile}`);
		const jsImport = relPath.replace(/\.d\.([cm]?)ts/, ".$1js");
		fileSavePromises.push(
			fs.writeFile(
				outFile,
				isTypeOnly
					? `${generatedHeader}export type * from "${relPath}";\n`
					: `${generatedHeader}export * from "${jsImport}";\n`,
				"utf8",
			),
		);
	}

	if (fileSavePromises.length === 0) {
		log.info(`\tNo Node10 compat files generated.`);
	}

	await Promise.all(fileSavePromises);
}
